////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNScripting/inc/WNTopLevelElement.h"
#include "WNScripting/inc/WNDataType.h"
#include "WNScripting/inc/WNInstruction.h"
#include "WNScripting/inc/WNFunctionDecl.h"
#include "WNScripting/inc/WNFunctionHeaderList.h"
#include "WNScripting/inc/WNFunctionManager.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4800)
    #pragma warning(disable: 4146)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4267)

    #if _WN_MSVC < 160000000
        #pragma warning(disable: 4985)
    #endif
#endif

#ifdef _WN_ANDROID
    #include <llvm/Support/IRBuilder.h>
    #include <llvm/LLVMContext.h>
#else
    #include <llvm/IRBuilder.h>
#endif

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

#include <llvm/Module.h>

using namespace WNScripting;

WNTopLevelElement::WNTopLevelElement(WN_SIZE_T _startLine,
                                     WN_SIZE_T _endLine,
                                     WN_SIZE_T _startChar,
                                     WN_SIZE_T _endChar,
                                     WN_BOOL _stackRet,
                                     WNDataType* _dataType,
                                     WNIdentifierElement _functionName,
                                     WNInstructionList* _instructionList) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mStackRet(_stackRet),
    mDataType(_dataType),
    mFunctionName(_functionName),
    mInstructionList(_instructionList),
    mFunctionHeaderList(WN_NULL) {
}

WNTopLevelElement::WNTopLevelElement(WN_SIZE_T _startLine,
                                     WN_SIZE_T _endLine,
                                     WN_SIZE_T _startChar,
                                     WN_SIZE_T _endChar,
                                     WN_BOOL _stackRet,
                                     WNDataType* _dataType,
                                     WNIdentifierElement _functionName,
                                     WNInstructionList* _instructionList,
                                     WNFunctionHeaderList* _headerList) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mStackRet(_stackRet),
    mDataType(_dataType),
    mFunctionName(_functionName),
    mInstructionList(_instructionList),
    mFunctionHeaderList(_headerList) {
}

WNTopLevelElementList::WNTopLevelElementList(WN_SIZE_T _startLine,
                  WN_SIZE_T _endLine,
                  WN_SIZE_T _startChar,
                  WN_SIZE_T _endChar) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mTopLevelElement(WN_NULL),
    mTopLevelElementList(WN_NULL) {
}

WNTopLevelElementList::WNTopLevelElementList(WN_SIZE_T _startLine,
                  WN_SIZE_T _endLine,
                  WN_SIZE_T _startChar,
                  WN_SIZE_T _endChar,
                  WNTopLevelElement* _topLevelElement,
                  WNTopLevelElementList* _topLevelElementList) :
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mTopLevelElement(_topLevelElement),
    mTopLevelElementList(_topLevelElementList) {
}

WNTopLevelElement::~WNTopLevelElement() {
    if (mDataType) {
        WN_DELETE(mDataType);
    }

    if (mFunctionHeaderList) {
        WN_DELETE(mFunctionHeaderList);
    }
}

WNTopLevelElementList::~WNTopLevelElementList() {
    if (mTopLevelElement) {
        WN_DELETE(mTopLevelElement);
    }

    if (mTopLevelElementList) {
        WN_DELETE(mTopLevelElementList);
    }
}

llvm::Value* WNTopLevelElementList::OptimizeAndGenerate(WNScriptingEngineData& _scriptingData, WNFunctionDecl* _functionDecl) {
    return(WNParseNode::OptimizeAndGenerate(_scriptingData, _functionDecl));
}

llvm::Value* WNTopLevelElement::OptimizeAndGenerate(WNScriptingEngineData& _scriptingData, WNFunctionDecl* _functionDecl) {
    return(WN_NULL);
}

WN_BOOL WNTopLevelElement::ReportErrors() {
    WN_BOOL retVal = WNParseNode::ReportErrors();

    for (std::deque<WNInstruction*>::iterator i = mInstructions.begin(); i != mInstructions.end(); ++i) {
         WNInstruction* inst = (*i);

         retVal &= inst->ReportErrors();
    }

    return(retVal);
}


llvm::Function* WNTopLevelElement::OptimizeAndGenerateFunction(WNScriptingEngineData& _scriptingData, WNFunction** _function) {
    llvm::IRBuilder<> * builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    WNParseNode::OptimizeAndGenerate(_scriptingData, WN_NULL);
    mDataType->OptimizeAndGenerate(_scriptingData, WN_NULL);
    WNFunction* ifunc = (*_function);
    WN_DEBUG_ASSERT(ifunc->mReturnType->mScriptType == mDataType->GetType());
    
    WNFunctionDecl f;
    f.mStackReturn = mStackRet;
    f.mReturnType = mDataType->GetType();
    f.mName = mFunctionName;
    f.mFileName = ifunc->mFile;
    WNInstructionList* l = mInstructionList;
    mInstructionList = WN_NULL;

    while(l != WN_NULL) {
        WNInstruction* inst = l->AppropriateInstruction();
        if (inst != WN_NULL) {
            mInstructions.push_front(inst);
        }
        WNInstructionList* l2 = l->AppropriateInstructionList();
        WN_DELETE(l);
        l = l2;
    }

    _scriptingData.mScopedVariableList->PushFunction(&f);
    std::vector<llvm::Type*> args;
    std::vector<WNFunctionHeaderList::WNFunctionVal> vals;
    if (mFunctionHeaderList) {
        mFunctionHeaderList->OptimizeAndGenerate(_scriptingData, WN_NULL);
        vals = mFunctionHeaderList->GetFunctionParameters();
        WN_DEBUG_ASSERT(vals.size() == ifunc->mTypes.size());
        for(size_t i = 0; i < vals.size(); ++i) {
            args.push_back(_scriptingData.mTypeManager->GetLLVMType(vals[i].mDataType));
            WN_DEBUG_ASSERT(vals[i].mDataType == ifunc->mTypes[i]->mScriptType);
        }
    }
    llvm::Function* func = ifunc->mLlvmFunction;
    f.mLLVMFunction = ifunc->mLlvmFunction;
    WN_UINT32 counter = 0;

    llvm::BasicBlock* block = llvm::BasicBlock::Create(llvm::getGlobalContext(), "InitialBlock", func);
    WN_DEBUG_ASSERT(block != WN_NULL);
    builder->SetInsertPoint(block);
    for(llvm::Function::arg_iterator fIter = func->arg_begin(); fIter != func->arg_end(); ++fIter, ++counter) {
        fIter->setName(_scriptingData.mIdentifierMap->GetStringFromIdentifier(vals[counter].mIdentifier));
        WNScriptVariable var;
        var.mName = vals[counter].mIdentifier;
        var.mType = vals[counter].mDataType;
        var.mLlvmAlloca = builder->CreateAlloca(_scriptingData.mTypeManager->GetLLVMType(vals[counter].mDataType), 0, _scriptingData.mIdentifierMap->GetStringFromIdentifier(vals[counter].mIdentifier));
        builder->CreateStore(fIter, var.mLlvmAlloca, WN_FALSE);
        _scriptingData.mScopedVariableList->PushVariable(var);
    }

    _scriptingData.mScopedVariableList->PushScopeBlock(_scriptingData);

    WN_BOOL returns = WN_FALSE;

    for (std::deque<WNInstruction*>::iterator i = mInstructions.begin(); i != mInstructions.end(); ++i) {
        WNInstruction* inst = (*i);

        if (inst->OptimizeAndGenerate(_scriptingData, &f) == WN_NULL) {
            return(WN_NULL);
        }

        returns = returns | inst->Returns();
    }

    _scriptingData.mScopedVariableList->PopScopeBlock(_scriptingData, !returns);
    _scriptingData.mScopedVariableList->PopFunction();

    return(func);
}
